WORKING_DIRECTORY = File.expand_path(File.dirname(__FILE__))

require "rubygems"
require "active_record"
require "yaml"
require "logger"
require "xmpp4r"
require "xmpp4r/roster"
require "timeout"

#load model
Dir[File.join(WORKING_DIRECTORY,"../app/models/*.rb")].each(){|f| require f}
#load msn
require File.join(WORKING_DIRECTORY,"msnm.rb")
#load daemon for linux
require File.join(WORKING_DIRECTORY,"daemon.rb")

class Robot < Daemon::Base

  DEFAULT_PORT=5222
  ADD_ANYONE=true
#  WORKING_DIRECTORY = File.expand_path(File.dirname(__FILE__))
  CONFIG_DIRECTORY=File.join(WORKING_DIRECTORY,"../config")
  XMPP_ACCOUNT_FILE=File.join(CONFIG_DIRECTORY,"xmpp_account.yml")
  DB_CONFIG_FILE=File.join(CONFIG_DIRECTORY,"database.yml")
  MSN_ACCOUNT_FILE=File.join(CONFIG_DIRECTORY,"msn_account.yml")

  @@xmpp_conn=nil
  @@roster=nil
  @@msnm=nil

  @@logger=Logger.new(File.join(WORKING_DIRECTORY,"../log/robot.log"),5, 1024000)

  def self.error_log(msg)
    @@logger.error("[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] #{msg}")
  end

  def self.warn_log(msg)
    @@logger.warn("[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] #{msg}")
  end

  def self.debug_log(msg)
    @@logger.debug("[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] #{msg}")
  end

  def self.exception_log(title,exp)
    if exp!=nil
      error_log(title+exp.message)
      exp.backtrace.each(){|e|error_log("#{e}")}
    end
  end

  MSNMessenger = Net::InstantMessaging::MSNMessenger

  class MSNSessionHandler < MSNMessenger::SessionHandler

    def handle_message( peer_id, peer_nick, msg_header, msg_body )
      begin
        if msg_header!=nil && msg_header =~ /\r\nContent-Type: text\/plain/um
          queue_message(Robot.process_command(msg_body,peer_id))
        end
      rescue => detail
        Robot.exception_log("process_command error:",detail)
      end
    end

    def on_join( peer_id, peer_nick )
      begin
        queue_message( 'hello ' + peer_id)
      rescue => detail
        Robot.exception_log("process_command error:",detail)
      end
    end

  end

  class MSNSessionHandlerFactory # Factory
    def create( msnm, session )
      MSNSessionHandler.new( msnm, session )
    end
  end

  def self.connect_msn
    if !File.readable?(MSN_ACCOUNT_FILE)
      error_log("#{MSN_ACCOUNT_FILE} can't be read")
      return
    end
    ac=YAML.load_file(MSN_ACCOUNT_FILE)["default_account"]
    if ac==nil
      error_log("no msn account!")
      return
    end
    @@msnm = MSNMessenger.new( ac["login_user"], ac["password"], MSNSessionHandlerFactory.new )
    @@msnm.ns.synchronize
    @@msnm.ns.online
  end
  
  def self.set_online_msn_with_retry_login(msn, restart=false)
    if restart
      begin
        Timeout::timeout(60) {
          msn.ns.logout
          Robot.debug_log("closed for msn")
        }        
      rescue => d
        Robot.exception_log("close msn error:",d)
      end
      begin
        Timeout::timeout(60) {
          connect_msn
          Robot.debug_log("restarted for msn")
        }        
      rescue => d
        Robot.exception_log("restart msn error:",d)
      end
    else
      begin
        Timeout::timeout(60) {
          msn.ns.synchronize
          msn.ns.online
          Robot.debug_log("set online for msn")
        }
      rescue => d
        Robot.exception_log("set online msn error:",d)
        begin
          Timeout::timeout(60) {
            connect_msn
            Robot.debug_log("reconnected for msn")
          }
        rescue => detail
          Robot.exception_log("reconnect failed, please restart. error:",detail)
        end
      end
    
    end
  end

  def self.connect_db
    if !File.readable?(DB_CONFIG_FILE)
      error_log("#{DB_CONFIG_FILE} can't be read")
      return
    end
    db=YAML.load_file(DB_CONFIG_FILE)["development"]
    if db==nil
      error_log("no db config!")
      return
    end
    ActiveRecord::Base.establish_connection(db)
    test_db()
  end
  
  def self.test_db()
    User.find :first
  end
  
  def self.test_db_with_retry_connect()
    Timeout::timeout(30) {
      test_db()
      Robot.debug_log("test db is ok")
    }
  rescue => d
    Robot.exception_log("test db error:",d)
    
    begin
      connect_db()
    rescue => detail
      Robot.exception_log("connect db error:",detail)
    end
  end

  def self.connect_xmpp
    if !File.readable?(XMPP_ACCOUNT_FILE)
      error_log("#{XMPP_ACCOUNT_FILE} can't be read")
      return
    end
    ac=YAML.load_file(XMPP_ACCOUNT_FILE)["default_account"]
    if ac==nil
      error_log("no xmpp account!")
      return
    end
    jid=Jabber::JID.new(ac["login_user"])
    j=Jabber::Client.new(jid)
    j.connect(ac["login_host"],ac["login_port"] || Robot::DEFAULT_PORT)
    #auth
    j.auth(ac["password"])
    #online
    p=Jabber::Presence.new(:chat)
    j.send(p)
    #receive message
    j.add_message_callback do |m|
      begin
        if m!=nil && m.body!=nil && !m.body.empty?
          email=Robot.split_email(m.from)
          msg_text=Robot.process_command(m.body,email)
          return_msg=Jabber::Message::new(m.from,msg_text)
          return_msg.type=m.type
          @@xmpp_conn.send(return_msg)
        else
          Robot.debug_log("empty message: #{m}")
        end
      rescue => detail
        Robot.exception_log("process_command error:",detail)
      end
    end
    #subscription
    @@roster = Jabber::Roster::Helper.new(j)

    @@roster.add_subscription_request_callback do|roster_item,presence|
      begin
        process_subscription(roster_item,presence)
      rescue => detail
        Robot.exception_log("process subscription error:",detail)
      end
    end
    @@xmpp_conn=j
  end
  
  def self.set_online_xmpp_with_retry_login(j, restart=false)
    if restart
      begin
        Timeout::timeout(60) {
          j.close()
          Robot.debug_log("closed for xmpp")
        }        
      rescue => d
        Robot.exception_log("close msn error:",d)
      end      
      begin
        Timeout::timeout(60) {
          connect_xmpp()
          Robot.debug_log("restarted for xmpp")
        }        
      rescue => d
        Robot.exception_log("restart msn error:",d)
      end      
    else
      begin
        Timeout::timeout(60) {
          p=Jabber::Presence.new(:chat)
          j.send(p)
          Robot.debug_log("set online for xmpp")
        }
      rescue => d
        Robot.exception_log("set online error:",d)
        begin
          Timeout::timeout(60) {
            connect_xmpp()
            Robot.debug_log("reconnected for xmpp")
          }
        rescue => detail
          Robot.exception_log("reconnect failed, please restart. error:",detail)
        end
      end
    end
  end

  def self.process_subscription(roster_item,presence)
    if ADD_ANYONE
      @@roster.accept_subscription(presence.from)
    else
      @@roster.decline_subscription(presence.from)
    end
#    debug_log(roster_item)
#    debug_log(presence)
  end

  def self.split_email(from)
    arr=from.to_s().split("/")
    arr[0]
  end

  #process command
  def self.process_command(cmd,email)
    debug_log("cmd:#{cmd} from:#{email}")
    #email=msg.from
    user=User.safe_get_user(email)
    if User.register_command?(cmd)
      m=User.get_register_url(email)
    elsif user
      m=WhereStore.process_command(cmd,user)
    else
      m=User.unregister_message()
    end
#    debug_log("command:#{cmd} from:#{email} return:#{m}")
    return m
  end

  def self.init_all
    begin
      connect_db()
    rescue => detail
      exception_log("connect database server error:",detail)
#      raise(detail)
    end
    debug_log("init db is done")
    begin
      connect_xmpp()
    rescue => detail
      exception_log("connect xmpp server error:",detail)
#      raise(detail)
    end
    debug_log("init xmpp is done")
    begin
      connect_msn()
    rescue => detail
      exception_log("connect msn server error:",detail)
#      raise(detail)
    end
    debug_log("init msn is done")
#    at_exit{ Robot.destory_all() }
    debug_log("init all is done")
  end

  def self.destory_all
    debug_log("destory all...")
    begin
      if @@xmpp_conn
        debug_log("close xmpp connection")
        @@xmpp_conn.close()
      end
      if @@msnm
        debug_log("close msn connection")
        @@msnm.logout
      end
    rescue => detail
      exception_log("disconnect im server error:",detail)
    end
  end

  def self.get_status
    "im server connection:#{@@xmpp_conn && @@xmpp_conn.is_connected?()} db server connection:#{ActiveRecord::Base.connected?}\n"
  end

  def self.prompt
    "input command(type 'help' to get help):"
  end

  def self.output(msg)
    puts(msg)
    debug_log(msg)
  end

  def self.start
    init_all()
    t=Time.now
    t1=Time.now
    while true
      #per 3 mins
      sleep(1)
      if Time.now - t > 180        
        test_db_with_retry_connect()
        if Time.now - t1 > 180*100
          set_online_xmpp_with_retry_login(@@xmpp_conn,true)
          set_online_msn_with_retry_login(@@msnm,true)
          t1=Time.now
        else  
          set_online_xmpp_with_retry_login(@@xmpp_conn,false)
          set_online_msn_with_retry_login(@@msnm,false)
        end
        t=Time.now
      end
    end
  end

  def self.stop
    destory_all()
  end

  def self.run
    output "init..."
    init_all()
    output get_status()
    output prompt()
    #loop
    while (cmd=gets.chomp().strip()) != ?\e do
      case cmd
      when "exit"
#        output "exit..."
#        destory_all()
        return
      when "status"
        output get_status
      when "help"
        output "\nexit - exit program\nstatus - show im server and db server connection\n"
      end
      output prompt()
    end
  end
end
